#include "CommProtocol.h"
#include "crc16.h"

typedef enum RequestType {
    RequestType_Auth = 0x01,
    RequestType_HeartBeat = 0x02,
    RequestType_Read = 0x03,
    RequestType_Write = 0x04,
    RequestType_OTA = 0x05,
}RequestType_t;

typedef enum ResponseType {
    ResponseType_Auth = 0x01,
    ResponseType_Read = 0x03,
    ResponseType_Write = 0x04,
    ResponseType_OTA = 0x05,
}ResponseType_t;

typedef struct CommProtocolRequestHeader {
    uint16_t magic_number;
    uint16_t auth_code;
    uint16_t type;
    uint16_t crc16;
    uint32_t length;
}CommProtocolRequestHeader_t;

typedef struct CommProtocolResponseHeader {
    uint16_t magic_number;
    uint16_t type;
    uint16_t crc16;
    uint16_t reserved;
    uint32_t length;
}CommProtocolResponseHeader_t;

static const uint16_t __magic_number = 0xaabb;
static const uint16_t comm_protocol_resp_ok = 1;
static const uint16_t comm_protocol_resp_failed = 0;
static const uint8_t comm_protocol_ota_type_start = 0x01;
static const uint8_t comm_protocol_ota_type_stop = 0x02;
static const uint8_t comm_protocol_ota_type_data = 0x03;
static const int comm_protocol_heartbeats_interval = 1000;

static CommProtocolPortable_t __portable;
static int __certified = 0;
static uint16_t __auth_key = 0x0000;
static int __heartbeat_remain = 0x00;

void CommProtocol_init(CommProtocolPortable_t portable){
    __portable = portable;
    __certified = 0;
}

typedef struct CommProtocolAuthRequestContent {
    uint8_t password[8];
}CommProtocolAuthRequestContent_t;

typedef struct CommProtocolAuthResponseContent {
    uint16_t status;
    uint16_t auth_key;
}CommProtocolAuthResponseContent_t;


static void CommProtocol_auth(char *content,int size){
    char response_buffer[sizeof(CommProtocolResponseHeader_t) + sizeof(CommProtocolAuthResponseContent_t)];
    if ( size < sizeof(CommProtocolAuthRequestContent_t) ) return;
    CommProtocolAuthRequestContent_t *auth_content = (CommProtocolAuthRequestContent_t*)content;
    CommProtocolResponseHeader_t *response_header = (CommProtocolResponseHeader_t*)response_buffer;
    response_header->magic_number = __magic_number;
    response_header->type = ResponseType_Auth;
    response_header->length = sizeof(CommProtocolAuthResponseContent_t);
    CommProtocolAuthResponseContent_t *response_content = (CommProtocolAuthResponseContent_t*)(response_buffer + sizeof(CommProtocolResponseHeader_t));
    if ( __portable.authenticate(auth_content->password) ){
        /* Authenticate pass. */
        __auth_key = __portable.randKey();
        response_content->status = comm_protocol_resp_ok;
        response_content->auth_key = __auth_key;
        __certified = 1;
        __heartbeat_remain = comm_protocol_heartbeats_interval;
    }else{
        response_content->status = comm_protocol_resp_failed;
    }
    response_header->crc16 = crc16(response_content,sizeof(CommProtocolAuthResponseContent_t));
    __portable.sendResponse(response_buffer,sizeof(CommProtocolResponseHeader_t) + sizeof(CommProtocolAuthResponseContent_t));
}

static void CommProtocol_heartBeat(char *content,int size){
    __heartbeat_remain = comm_protocol_heartbeats_interval;
}

typedef struct CommProtocolReadRequestContent {
    int index;
}CommProtocolReadRequestContent_t;

static void CommProtocol_read(char *content,int size){
    if ( size < sizeof(CommProtocolReadRequestContent_t) ) return;
    CommProtocolReadRequestContent_t *read_content = (CommProtocolReadRequestContent_t*)content;
    char response_buffer[64];
    CommProtocolResponseHeader_t *response_header = (CommProtocolResponseHeader_t*)response_buffer;
    int read_size;
    __portable.readData(read_content->index,response_buffer + sizeof(CommProtocolResponseHeader_t),&read_size);
    response_header->magic_number = __magic_number;
    response_header->type = ResponseType_Read;
    response_header->length = read_size;
    response_header->crc16 = crc16(response_buffer + sizeof(CommProtocolResponseHeader_t),read_size);
    __portable.sendResponse(response_buffer,sizeof(CommProtocolResponseHeader_t) + read_size);
}

typedef struct CommProtocolWriteRequestContent {
    int index;
}CommProtocolWriteRequestContent_t;

typedef struct CommProtocolWriteResponseContent {
    int index;
}CommProtocolWriteResponseContent_t;

static void CommProtocol_write(char *content,int size){
    CommProtocolWriteRequestContent_t *write_content = (CommProtocolWriteRequestContent_t*)content;
    char response_buffer[64];
    CommProtocolResponseHeader_t *response_header = (CommProtocolResponseHeader_t*)response_buffer;
    CommProtocolWriteResponseContent_t *response_content = (CommProtocolWriteResponseContent_t*)response_buffer + sizeof(CommProtocolResponseHeader_t);
    __portable.writeData(write_content->index,content + sizeof(CommProtocolWriteRequestContent_t),size - sizeof(CommProtocolWriteRequestContent_t));
    response_header->magic_number = __magic_number;
    response_header->type = ResponseType_Write;
    response_header->length = sizeof(CommProtocolWriteResponseContent_t);
    response_content->index = write_content->index;
    response_header->crc16 = crc16(response_content,sizeof(CommProtocolWriteResponseContent_t));
    __portable.sendResponse(response_buffer,sizeof(CommProtocolResponseHeader_t) + sizeof(CommProtocolResponseHeader_t));
}

typedef struct CommProtocolOTARequestContent {
    uint8_t type;
}CommProtocolOTARequestContent_t;

typedef struct CommProtocolOTAResponseContent {
    uint8_t status;
}CommProtocolOTAResponseContent_t;

static void CommProtocol_OTA(char *content,int size){
    static uint32_t ota_address_offset = 0x00000000;
    char *response_buffer[64];
    CommProtocolOTARequestContent_t *ota_content = (CommProtocolOTARequestContent_t*)content;
    CommProtocolResponseHeader_t *response_header = (CommProtocolResponseHeader_t*)response_buffer;
    CommProtocolOTAResponseContent_t *response_content = (CommProtocolOTAResponseContent_t*)response_buffer + sizeof(CommProtocolResponseHeader_t);
    switch ( ota_content->type ){
        case comm_protocol_ota_type_start: 
            ota_address_offset = 0x00000000;
            response_content->status = comm_protocol_resp_ok;
            break;
        case comm_protocol_ota_type_data:
            __portable.writeOTAData(ota_address_offset,content + sizeof(CommProtocolOTARequestContent_t),size - sizeof(CommProtocolOTARequestContent_t));
            ota_address_offset += size - sizeof(CommProtocolOTARequestContent_t);
            response_content->status = comm_protocol_resp_ok;
            break;
        case comm_protocol_ota_type_stop:
            __portable.startOTA();
            response_content->status = comm_protocol_resp_ok;
            break;
        default:
            response_content->status = comm_protocol_resp_failed;
            break;
    }
    __portable.sendResponse(response_buffer,sizeof(CommProtocolResponseHeader_t) + sizeof(CommProtocolOTAResponseContent_t));
}

int CommProtocol_parseRequest(void *buffer){
    CommProtocolRequestHeader_t *request_header = (CommProtocolRequestHeader_t*)buffer;
    uint8_t *content = ((uint8_t*)buffer) + sizeof(CommProtocolRequestHeader_t);
    if ( request_header->magic_number != __magic_number ) return Error_UnknownFormat;
    if ( __certified == 0 && request_header->type != RequestType_Auth ) return Error_Illegal;
    if ( __certified != 0 && request_header->type == RequestType_Auth ) return Error_Illegal;
    if ( request_header->crc16 != crc16(content,request_header->length) ) return Error_CRCFailed;

    switch ( request_header->type ){
        case RequestType_Auth: CommProtocol_auth(content,request_header->length); break;
        case RequestType_HeartBeat: CommProtocol_heartBeat(content,request_header->length); break;
        case RequestType_Read: CommProtocol_read(content,request_header->length); break;
        case RequestType_Write: CommProtocol_write(content,request_header->length); break;
        case RequestType_OTA: CommProtocol_OTA(content,request_header->length); break;
    }
    return Error_None;
}

void CommProtocol_loop(){
    if ( __heartbeat_remain > 0 ){
        __heartbeat_remain--;
    }else{
        /* Connection lost. */
        __certified = 0;
    }
}
